/* Copyright (c) 2022 渝州大数据实验室
 *
 * Lanius is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.yzbdl.lanius.orchestrate.serv.service.resource.impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.json.JSONUtil;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.yzbdl.lanius.orchestrate.serv.dataBase.DynamicDataBase;
import org.yzbdl.lanius.orchestrate.serv.dto.resource.*;
import org.yzbdl.lanius.orchestrate.serv.entity.resource.TaskResourceConfigEntity;
import org.yzbdl.lanius.orchestrate.serv.entity.resource.TaskResourceEntity;
import org.yzbdl.lanius.orchestrate.serv.entity.task.TaskPlan;
import org.yzbdl.lanius.orchestrate.serv.enums.ResourceTypeEnum;
import org.yzbdl.lanius.orchestrate.serv.mapper.resource.TaskResourceConfigMapper;
import org.yzbdl.lanius.orchestrate.serv.mapper.resource.TaskResourceMapper;
import org.yzbdl.lanius.orchestrate.serv.mapper.task.TaskPlanMapper;
import org.yzbdl.lanius.orchestrate.serv.service.resource.TaskResourceService;
import org.yzbdl.lanius.orchestrate.serv.utils.JdbcUtil;
import org.yzbdl.lanius.orchestrate.serv.utils.TreeUtil;
import org.yzbdl.lanius.orchestrate.common.base.dto.DataBaseInfo;
import org.yzbdl.lanius.orchestrate.common.exception.runtime.BusinessException;
import org.yzbdl.lanius.orchestrate.common.utils.ExceptionUtil;
import org.yzbdl.lanius.orchestrate.common.utils.SpecialCharacterUtil;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.yzbdl.lanius.orchestrate.serv.constant.TaskResourceConstant.*;

/**
 * @author zhuhongji@yzbdl.ac.cn
 * @since 2022-04-08 16:49
 */
@Service
@AllArgsConstructor
public class TaskResourceServiceImpl extends ServiceImpl<TaskResourceMapper, TaskResourceEntity> implements TaskResourceService {

    private static final Logger logger = LoggerFactory.getLogger(TaskResourceServiceImpl.class);

    private final TaskResourceConfigMapper taskResourceConfigMapper;

    private final TaskPlanMapper taskPlanMapper;

    @Override
    public Boolean addTaskResource(TaskResourceDto taskResourceDto) {
        checkResourceType(taskResourceDto.getResourceType(), taskResourceDto.getResourcePath());
        // 重命名判断
        LambdaQueryWrapper<TaskResourceEntity> queryWrapper = new QueryWrapper<TaskResourceEntity>().lambda()
                .eq(TaskResourceEntity::getResourceName, taskResourceDto.getResourceName())
                .eq(TaskResourceEntity::getDeleted, false);
        ExceptionUtil.checkParam(!this.baseMapper.exists(queryWrapper), "已存在同样的资源名称，请重新输入！");
        // 检测资源库是否存在
        this.checkResourceConfigExists(taskResourceDto.getResourceConfigId());
        // Dto数据拷贝
        TaskResourceEntity taskResourceEntity = new TaskResourceEntity();
        BeanUtils.copyProperties(taskResourceDto, taskResourceEntity);
        taskResourceEntity.setDeleted(false);
        // 写入资源配置数据
        this.setResourceContent(taskResourceDto, taskResourceEntity);
        return this.save(taskResourceEntity);
    }

    /**
     * 检测任务资源是否存在
     *
     * @param taskResourceConfigId 任务资源id
     */
    private void checkResourceConfigExists(Long taskResourceConfigId) {
        if (Objects.nonNull(taskResourceConfigId)) {
            LambdaQueryWrapper<TaskResourceConfigEntity> resourceConfigWrapper = new QueryWrapper<TaskResourceConfigEntity>().lambda()
                    .eq(TaskResourceConfigEntity::getId, taskResourceConfigId)
                    .eq(TaskResourceConfigEntity::getDeleted, false);
            ExceptionUtil.checkParam(taskResourceConfigMapper.exists(resourceConfigWrapper), "所选资源配置已删除请重新选择！");
        }
    }

    @Override
    public Boolean deleteTaskResource(Long id) {
        LambdaQueryWrapper<TaskPlan> queryWrapper = new QueryWrapper<TaskPlan>().lambda()
                .eq(TaskPlan::getTaskResourceId, id);
        if (taskPlanMapper.exists(queryWrapper)) {
            throw new BusinessException("该任务资源已被调用，无法删除！");
        }
        return this.updateById(TaskResourceEntity.builder().id(id).deleted(true).build());
    }

    @Override
    public TaskResourceEntity getTaskResource(Long id) {
        return this.getById(id);
    }

    @Override
    public IPage<TaskResourceDto> pageTaskResource(Integer page, Integer size, TaskResourcePageDto taskResource) {
        // 1. 分页获取任务资源数据
        Page<TaskResourceEntity> pageParam = new Page<>(page, size);
        LambdaQueryWrapper<TaskResourceEntity> queryWrapper = new QueryWrapper<TaskResourceEntity>().lambda()
                // 资源类型条件
                .eq(Objects.nonNull(taskResource.getResourceType()), TaskResourceEntity::getResourceType,
                        taskResource.getResourceType())
                // 资源名称匹配
                .like(StringUtils.hasLength(taskResource.getResourceName()), TaskResourceEntity::getResourceName,
                        SpecialCharacterUtil.escapeStr(taskResource.getResourceName()))
                // 时间查询条件
                .between(Objects.nonNull(taskResource.getStartTime()) && Objects.nonNull(taskResource.getEndTime()),
                        TaskResourceEntity::getCreateTime, taskResource.getStartTime(), taskResource.getEndTime())
                // 匹配分组信息
                .eq(Objects.nonNull(taskResource.getGroupId()), TaskResourceEntity::getGroupId, taskResource.getGroupId())
                // 筛选未删除数据
                .eq(TaskResourceEntity::getDeleted, false)
                // 创建时间降序排序
                .orderByDesc(TaskResourceEntity::getCreateTime)
                .orderByDesc(TaskResourceEntity::getId);
        Page<TaskResourceEntity> pageList = this.page(pageParam, queryWrapper);
        // 2. 获取配置信息
        List<Long> ids = pageList.getRecords().stream().map(TaskResourceEntity::getResourceConfigId).distinct()
                .collect(Collectors.toList());
        List<TaskResourceConfigEntity> taskEntities =
                !CollectionUtils.isEmpty(ids) ? taskResourceConfigMapper.selectBatchIds(ids) : new ArrayList<>();
        // 3. 转换为配置Map信息
        Map<Long, TaskResourceConfigEntity> configMap = taskEntities.stream()
                .collect(Collectors.toMap(TaskResourceConfigEntity::getId, Function.identity(), (o1, o2) -> o1));
        // 4. 拼装配置信息
        return this.getConvert(pageList, configMap);
    }

    @Override
    public Boolean isExistsTaskResource(Long id) {
        LambdaQueryWrapper<TaskResourceEntity> queryWrapper = new QueryWrapper<TaskResourceEntity>().lambda()
                .eq(TaskResourceEntity::getGroupId, id)
                .eq(TaskResourceEntity::getDeleted, false);
        // 判断当前目录是否存在内容
        return this.baseMapper.exists(queryWrapper);
    }

    @Override
    public Boolean updateTaskResource(TaskResourceDto taskResourceDto) {
        checkResourceType(taskResourceDto.getResourceType(), taskResourceDto.getResourcePath());
        ExceptionUtil.checkParam(Objects.nonNull(taskResourceDto.getId()), "id不能为空");
        // 重命名判断
        LambdaQueryWrapper<TaskResourceEntity> queryWrapper = new QueryWrapper<TaskResourceEntity>().lambda()
                .ne(TaskResourceEntity::getId, taskResourceDto.getId())
                .eq(TaskResourceEntity::getResourceName, taskResourceDto.getResourceName())
                .eq(TaskResourceEntity::getDeleted, false);
        ExceptionUtil.checkParam(!this.baseMapper.exists(queryWrapper), "已存在同样的资源名称，请重新输入！");
        // 检测资源库是否存在
        this.checkResourceConfigExists(taskResourceDto.getResourceConfigId());
        // Dto数据拷贝
        TaskResourceEntity taskResourceEntity = new TaskResourceEntity();
        BeanUtils.copyProperties(taskResourceDto, taskResourceEntity);
        // 写入资源配置数据
        this.setResourceContent(taskResourceDto, taskResourceEntity);
        return this.updateById(taskResourceEntity);
    }

    /**
     * 匹配资源类型与资源路径
     *
     * @param resourceType 资源类型
     * @param resourcePath 资源路径
     */
    private void checkResourceType(Integer resourceType, String resourcePath) {
        boolean ktrCheck = ResourceTypeEnum.KETTLE_KTR.getCode().equals(resourceType) &&
                resourcePath.endsWith(ResourceTypeEnum.KETTLE_KTR.getValue());
        boolean jobCheck = ResourceTypeEnum.KETTLE_OBJ.getCode().equals(resourceType) &&
                resourcePath.endsWith(ResourceTypeEnum.KETTLE_OBJ.getValue());
        if (!(ktrCheck || jobCheck)) {
            throw new BusinessException("操作失败，请注意资源类型与所选资源相匹配！");
        }
    }

    @Override
    public void batchUpdateResourceContent(Long resourceConfigId, String repoName) {
        LambdaQueryWrapper<TaskResourceEntity> queryWrapper = new QueryWrapper<TaskResourceEntity>().lambda()
                .eq(TaskResourceEntity::getResourceConfigId, resourceConfigId);
        List<TaskResourceEntity> list = this.list(queryWrapper);
        if (!CollectionUtils.isEmpty(list)) {
            List<TaskResourceEntity> updateList = list.stream().map(entity -> {
                Map<String, String> contentMap = JSONUtil.toBean(JSONUtil.parse(entity.getResourceContent()), new TypeReference<>() {
                }, true);
                contentMap.put(CONFIG_CONTENT_REPO_NAME, repoName);
                return TaskResourceEntity.builder()
                        .id(entity.getId())
                        .resourceContent(JSONUtil.toJsonStr(contentMap))
                        .build();
            }).collect(Collectors.toList());
            this.updateBatchById(updateList);
        }
    }

    /**
     * 设定资源配置字段
     *
     * @param taskResourceDto    taskResourceDto
     * @param taskResourceEntity taskResourceEntity
     */
    private void setResourceContent(TaskResourceDto taskResourceDto, TaskResourceEntity taskResourceEntity) {
        Map<String, String> resourceContent = new HashMap<>(4);
        TaskResourceConfigEntity taskResourceConfigEntity = taskResourceConfigMapper.selectById(taskResourceDto.getResourceConfigId());
        ExceptionUtil.checkParam(Objects.nonNull(taskResourceConfigEntity), "未查询到对应数据库信息！");
        // 构建资源库Json数据
        resourceContent.put(CONFIG_CONTENT_REPO_NAME, JdbcUtil.getDbName(JdbcUtil.parseJdbcUrl(taskResourceConfigEntity.getConnectUrl())));
        resourceContent.put(CONFIG_CONTENT_REPO_ACCOUNT, taskResourceDto.getResourceAccountName());
        resourceContent.put(CONFIG_CONTENT_REPO_PASSWORD, taskResourceDto.getResourcePassword());
        resourceContent.put(CONFIG_CONTENT_REPO_ENTRANCE, taskResourceDto.getResourcePath());
        taskResourceEntity.setResourceContent(JSONUtil.toJsonStr(resourceContent));
    }

    @Override
    public List<TaskResourceEntity> getListByGroupIdForTaskPlan(Long resourceGroupId) {
        LambdaQueryWrapper<TaskResourceEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TaskResourceEntity::getGroupId, resourceGroupId);
        queryWrapper.eq(TaskResourceEntity::getDeleted, Boolean.FALSE);
        queryWrapper.select(TaskResourceEntity::getId, TaskResourceEntity::getGroupId, TaskResourceEntity::getResourceName);
        return this.list(queryWrapper);
    }

    @Override
    public List<ExternalDirectoryTreeDto> buildExternalResourceTree(Long resourceConfigId) {
        DruidDataSource ds = this.getDataSource(resourceConfigId);
        try {
            Db db = Db.use(ds);
            // 获取外部节点数据
            List<ExternalDirectoryDto> directoryDtoLists = db.findAll(Entity.create(DIRECTORY_TABLE_NAME), ExternalDirectoryDto.class);
            // 拼装树结构
            List<ExternalDirectoryTreeDto> treeNodes = directoryDtoLists.stream().map(directory -> ExternalDirectoryTreeDto
                    .builder()
                    .id(directory.getIdDirectory())
                    .parentId(directory.getIdDirectoryParent())
                    .nodeName(directory.getDirectoryName())
                    .build()).collect(Collectors.toList());
            return TreeUtil.buildDetailByRoot(treeNodes, 0L);
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new BusinessException("远程资源库连接异常，请检查资源库！");
        } finally {
            try {
                // 数据库连接关闭
                ds.close();
            } catch (Exception e) {
                logger.error("动态数据库关闭异常！", e);
            }
        }
    }

    @Override
    public ExternalFileDto getExternalResourceFile(Long directoryId, Long resourceConfigId, Integer resourceType) {
        DruidDataSource ds = this.getDataSource(resourceConfigId);
        try {
            Db db = Db.use(ds);
            List<ExternalJobDto> jobs = new ArrayList<>();
            List<ExternalTransformationDto> transformations = new ArrayList<>();
            if (Objects.isNull(resourceType)) {
                transformations = getTransformationList(directoryId, db);
                jobs = getExternalJobList(directoryId, db);
            } else {
                switch (resourceType) {
                    // 作业
                    case 1:
                        jobs = getExternalJobList(directoryId, db);
                        break;
                    // 转换
                    case 2:
                        transformations = getTransformationList(directoryId, db);
                        break;
                    // 默认值
                    default:
                        transformations = getTransformationList(directoryId, db);
                        jobs = getExternalJobList(directoryId, db);
                }
            }
            return ExternalFileDto.builder().jobs(jobs).transformations(transformations).build();
        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new BusinessException("远程资源库连接异常，请检查资源库！");
        } finally {
            try {
                // 数据库连接关闭
                ds.close();
            } catch (Exception e) {
                logger.error("动态数据库关闭异常！", e);
            }
        }
    }

    /**
     * 获取外部任务数据
     *
     * @param directoryId 目录id
     * @param db          数据库连接
     * @return 任务数据
     */
    private List<ExternalJobDto> getExternalJobList(Long directoryId, Db db) throws java.sql.SQLException {
        return db.findAll(Entity.create(JOB_TABLE_NAME).set("ID_DIRECTORY", directoryId), ExternalJobDto.class);
    }

    /**
     * 获取外部转换数据
     *
     * @param directoryId 目录id
     * @param db          数据库连接
     * @return 转换数据
     */
    private List<ExternalTransformationDto> getTransformationList(Long directoryId, Db db) throws java.sql.SQLException {
        // 获取transformation数据
        return db.findAll(Entity.create(TRANSFORMATION_TABLE_NAME).set("ID_DIRECTORY", directoryId),
                ExternalTransformationDto.class);
    }


    /**
     * 通过资源配置项id获取连接数据
     *
     * @param resourceConfigId 资源库id
     * @return 资源连接信息
     */
    private DruidDataSource getDataSource(Long resourceConfigId) {
        TaskResourceConfigEntity taskResourceConfigEntity = taskResourceConfigMapper.selectById(resourceConfigId);
        ExceptionUtil.checkParam(Objects.nonNull(taskResourceConfigEntity), "未找到对应数据库资源！");
        DataBaseInfo dataBaseInfo = DataBaseInfo.builder()
                .url(taskResourceConfigEntity.getConnectUrl())
                .userName(taskResourceConfigEntity.getConnectAccount())
                .password(taskResourceConfigEntity.getConnectPassword()).build();
        return DynamicDataBase.getDruidDataSource(dataBaseInfo);
    }

    /**
     * taskResource数据转换
     *
     * @param pageList  entityList数据
     * @param configMap map配置
     * @return TaskResourceDto数据
     */
    private IPage<TaskResourceDto> getConvert(Page<TaskResourceEntity> pageList, Map<Long, TaskResourceConfigEntity> configMap) {
        return pageList.convert(task -> {
            TaskResourceDto taskResourceDto = new TaskResourceDto();
            BeanUtils.copyProperties(task, taskResourceDto);
            taskResourceDto.setConfigConnectName(Optional
                    .ofNullable(configMap.get(taskResourceDto.getResourceConfigId()))
                    .map(TaskResourceConfigEntity::getConnectName)
                    .orElse(null));
            return taskResourceDto;
        });
    }
}